Skip to content

feat: add balancec-watcher updater#7640

Open
limitofzero wants to merge 20 commits into
developfrom
feat/balance-watcher-updater-2
Open

feat: add balancec-watcher updater#7640
limitofzero wants to merge 20 commits into
developfrom
feat/balance-watcher-updater-2

Conversation

@limitofzero

@limitofzero limitofzero commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires the cowswap-frontend to the new balances-watcher SSE service, behind the LaunchDarkly flag isBwEnabled.

When the flag is on, balances are pushed in real time over an EventSource and the existing multicall pipeline (priority tokens + full token-list polling) is bypassed. When off, nothing changes — the multicall path stays as is.

Note on the native token (ETH / xDAI / MATIC / BNB): the watcher service does not emit native balances, so we keep polling them via a single eth_getBalance RPC call (wagmi useBalance), refetched every 11 seconds.
This means the native balance can lag up to 11s behind the on-chain state — that's an explicit trade-off because I reused current implementation, but if it's crucial - I can rework it.

Out of scope (planned for follow-up):

  • Fallback to multicall when the SSE stream terminates with an unrecoverable error.

To Test

  1. Open the swap widget and switch on isBwEnabled flag.
  • Network tab shows a single POST /sessions followed by an open EventSource to the balances-watcher endpoint - and it returns 200 (OK)
  • Balances of imported tokens / tokens from enabled lists are visible
  • try to swap - balances should be updated very fast, about 0-2s after order execution
  • if user switch on token list/or add custom token - there should be another /sessions call with new token list or custom token in parameters(body), the balance of these tokens should be updated too

Summary by CodeRabbit

Release Notes

  • New Features

    • Added real-time balance watcher functionality, toggled via feature flag, for enhanced balance synchronization.
  • Tests

    • Added comprehensive test coverage for balance watcher session management, native token balance updates, and custom token handling.
  • Chores

    • Introduced new internal hooks and updater components to support balance watcher infrastructure and token list configuration.

@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Jun 16, 2026 10:01pm
storybook Ready Ready Preview Jun 16, 2026 10:01pm
widget-configurator Ready Ready Preview Jun 16, 2026 10:01pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jun 16, 2026 10:01pm
sdk-tools Ignored Ignored Preview Jun 16, 2026 10:01pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds an SSE-driven balances watcher mode to the balances-and-allowances library. New hooks collect enabled token list URLs and per-chain custom token addresses, a session hook manages the SSE lifecycle with stale-update cancellation, and a NativeTokenBalanceUpdater writes native balances to Jotai state. A new BalancesWatcherUpdater component composes these pieces and is wired into CommonPriorityBalancesAndAllowancesUpdater behind a isBwEnabled feature flag.

Changes

SSE Balances Watcher Mode

Layer / File(s) Summary
Token input hooks
libs/balances-and-allowances/src/hooks/useEnabledTokensListsUrls.ts, libs/balances-and-allowances/src/hooks/useCustomTokensForChain.ts, libs/balances-and-allowances/src/hooks/useCustomTokensForChain.test.tsx
useEnabledTokensListsUrls returns sorted enabled token list URLs; useCustomTokensForChain returns memoized sorted normalized address keys for user-added tokens on a given chain. Tests cover filtering, normalization, sorting, and empty inputs.
NativeTokenBalanceUpdater
libs/balances-and-allowances/src/updaters/NativeTokenBalanceUpdater.tsx, libs/balances-and-allowances/src/updaters/NativeTokenBalanceUpdater.test.tsx
New component reads the native token balance via useNativeTokenBalance and writes it into balancesAtom keyed by the chain's native token address. Tests verify no write before data, correct key usage, prop forwarding, and rerender updates.
useBalancesWatcherSession hook
libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts, libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx
Implements the SSE session lifecycle: guards on account/EVM chain/non-empty tokens, creates a session, subscribes to events, merges balance diffs into balancesAtom with bigint conversion and address normalization, surfaces only terminal errors, and closes subscriptions on cleanup. Tests cover guard conditions, session payload shape, diff merging, terminal vs. non-terminal errors, session rejection, unmount cleanup, and a race-guard for stale chainId changes.
BalancesWatcherUpdater component and export
libs/balances-and-allowances/src/updaters/BalancesWatcherUpdater.tsx, libs/balances-and-allowances/src/index.ts
New component composes the two input hooks and useBalancesWatcherSession, then renders BalancesResetUpdater, BalancesCacheUpdater, and NativeTokenBalanceUpdater. Re-exported from the library's public index.
Feature-flag short-circuit
apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx
Imports BalancesWatcherUpdater, reads isBwEnabled from useFeatureFlags, and early-returns the watcher when enabled instead of the existing combined priority updaters.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(100, 149, 237, 0.5)
    note over CommonPriorityBalancesAndAllowancesUpdater: Feature flag active
    CommonPriorityBalancesAndAllowancesUpdater->>BalancesWatcherUpdater: render(account, chainId)
  end
  BalancesWatcherUpdater->>useEnabledTokensListsUrls: get enabled list URLs
  BalancesWatcherUpdater->>useCustomTokensForChain: get custom token addresses
  BalancesWatcherUpdater->>useBalancesWatcherSession: start(account, chainId, listUrls, customTokens)
  useBalancesWatcherSession->>createBalancesWatcherSession: POST session
  createBalancesWatcherSession-->>useBalancesWatcherSession: sessionId
  useBalancesWatcherSession->>subscribeToBalancesEvents: open SSE stream
  subscribeToBalancesEvents-->>useBalancesWatcherSession: balance diff event
  useBalancesWatcherSession->>balancesAtom: merge update (BigInt, normalized addresses)
  BalancesWatcherUpdater->>NativeTokenBalanceUpdater: render(account, chainId)
  NativeTokenBalanceUpdater->>balancesAtom: write native token balance
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • cowprotocol/cowswap#7551: Modifies CommonPriorityBalancesAndAllowancesUpdater.tsx to replace the legacy BFF-based update path, directly preceding the watcher short-circuit added in this PR.
  • cowprotocol/cowswap#7587: Changes useNativeTokenBalance to disable balance queries for Solana, which directly impacts the new native balance updater and session-watching logic introduced here.

Suggested labels

Balances

Suggested reviewers

  • elena-zh
  • Danziger

🐇 Hop hop, the balances stream in live,
No more polling just to stay alive!
SSE whispers each token's new weight,
Big integers normalized, sorted just great.
A flag flips, the watcher takes the lead —
This bunny approves at lightning speed! ⚡

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title contains a typo ('balancec-watcher' instead of 'balances-watcher') and is partially related to the changeset, referring to the watcher updater component but missing the key context about feature flagging and SSE integration. Correct the typo and clarify the title to reflect the main change, e.g., 'feat: add balances-watcher SSE integration behind feature flag' or 'feat: integrate balances-watcher service with isBwEnabled flag'.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR description provides clear summary, testing steps with checkboxes, and relevant background on implementation and trade-offs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/balance-watcher-updater-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@limitofzero limitofzero changed the title feat: add bw updater feat: add balancec-watcher updater Jun 10, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: ab8147b
Status: ✅  Deploy successful!
Preview URL: https://4ac32874.swap-dev-5u6.pages.dev
Branch Preview URL: https://feat-balance-watcher-updater.swap-dev-5u6.pages.dev

View logs

…-watcher-updater-2

# Conflicts:
#	libs/balances-and-allowances/src/balancesWatcher/subscribeToBalancesEvents.test.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying explorer-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: ab8147b
Status: ✅  Deploy successful!
Preview URL: https://3a119947.explorer-dev-dxz.pages.dev
Branch Preview URL: https://feat-balance-watcher-updater.explorer-dev-dxz.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx (1)

25-61: ⚡ Quick win

Avoid running legacy updater hooks when watcher mode is enabled.

usePriorityTokenAddresses, timer effects, and useOrdersFilledEventsTrigger are executed before the isBwEnabled return, so watcher mode still initializes legacy multicall-path logic. Split the watcher and legacy paths into separate components and branch immediately after reading the flag.

Proposed refactor
 export function CommonPriorityBalancesAndAllowancesUpdater(): ReactNode {
   const sourceChainId = useSourceChainId().chainId
   const { account } = useWalletInfo()
   const balancesContext = useBalancesContext()
   const balancesAccount = balancesContext.account || account
   const { isBwEnabled } = useFeatureFlags()
-
-  const priorityTokenAddresses = usePriorityTokenAddresses()
-  const priorityTokenAddressesAsArray = useMemo(() => {
-    return Array.from(priorityTokenAddresses.values())
-  }, [priorityTokenAddresses])
-  const priorityTokenCount = priorityTokenAddressesAsArray.length
-  const [skipFirstPriorityUpdate, setSkipFirstPriorityUpdate] = useState(true)
-  useEffect(() => {
-    setSkipFirstPriorityUpdate(true)
-  }, [sourceChainId])
-  useEffect(() => {
-    if (!account || !priorityTokenCount) return
-    const timeout = setTimeout(() => {
-      setSkipFirstPriorityUpdate(false)
-    }, PRIORITY_TOKENS_REFRESH_INTERVAL)
-    return () => clearTimeout(timeout)
-  }, [account, priorityTokenCount])
-  const refreshTrigger = useOrdersFilledEventsTrigger()

   if (isBwEnabled) {
     return <BalancesWatcherUpdater account={balancesAccount} chainId={sourceChainId} />
   }
+
+  return <LegacyPriorityBalancesUpdater account={account} balancesAccount={balancesAccount} sourceChainId={sourceChainId} />
+}
+
+function LegacyPriorityBalancesUpdater({
+  account,
+  balancesAccount,
+  sourceChainId,
+}: {
+  account: string | undefined
+  balancesAccount: string | undefined
+  sourceChainId: number
+}): ReactNode {
+  const priorityTokenAddresses = usePriorityTokenAddresses()
+  const priorityTokenAddressesAsArray = useMemo(() => Array.from(priorityTokenAddresses.values()), [priorityTokenAddresses])
+  const priorityTokenCount = priorityTokenAddressesAsArray.length
+  const [skipFirstPriorityUpdate, setSkipFirstPriorityUpdate] = useState(true)
+  const refreshTrigger = useOrdersFilledEventsTrigger()
+  // existing effects and return JSX remain here
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx`
around lines 25 - 61, The legacy updater hooks including
usePriorityTokenAddresses, the timer effects with
PRIORITY_TOKENS_REFRESH_INTERVAL, and useOrdersFilledEventsTrigger are being
executed unconditionally before checking the isBwEnabled flag. Move the check
for isBwEnabled to the top of the component and return the
BalancesWatcherUpdater immediately when watcher mode is enabled, then wrap all
the legacy hook calls and effects (usePriorityTokenAddresses, the two useEffect
blocks, and useOrdersFilledEventsTrigger) in a conditional that only executes
when isBwEnabled is false. This ensures legacy multicall-path logic is not
initialized when watcher mode is active.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx`:
- Around line 126-135: The test is using toLowerCase() to normalize the TOKEN_A
address, which violates the repo's address-handling guidelines. Replace
TOKEN_A.toLowerCase() with getAddressKey(TOKEN_A) in two places: in the
renderSession call where customTokens array is passed with the lowercased token,
and in the mockCreateSession assertion where customTokens is expected in the
body. Import getAddressKey from `@cowprotocol/cow-sdk` if not already imported,
and ensure both the input passed to renderSession and the expected value in the
assertion use getAddressKey(TOKEN_A) for consistent address normalization.

---

Nitpick comments:
In
`@apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx`:
- Around line 25-61: The legacy updater hooks including
usePriorityTokenAddresses, the timer effects with
PRIORITY_TOKENS_REFRESH_INTERVAL, and useOrdersFilledEventsTrigger are being
executed unconditionally before checking the isBwEnabled flag. Move the check
for isBwEnabled to the top of the component and return the
BalancesWatcherUpdater immediately when watcher mode is enabled, then wrap all
the legacy hook calls and effects (usePriorityTokenAddresses, the two useEffect
blocks, and useOrdersFilledEventsTrigger) in a conditional that only executes
when isBwEnabled is false. This ensures legacy multicall-path logic is not
initialized when watcher mode is active.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7a9bbe55-4a04-4a33-8564-4fc21c765483

📥 Commits

Reviewing files that changed from the base of the PR and between bb0bd33 and 73520a7.

📒 Files selected for processing (10)
  • apps/cowswap-frontend/src/modules/balancesAndAllowances/updaters/CommonPriorityBalancesAndAllowancesUpdater.tsx
  • libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx
  • libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.ts
  • libs/balances-and-allowances/src/hooks/useCustomTokensForChain.test.tsx
  • libs/balances-and-allowances/src/hooks/useCustomTokensForChain.ts
  • libs/balances-and-allowances/src/hooks/useEnabledTokensListsUrls.ts
  • libs/balances-and-allowances/src/index.ts
  • libs/balances-and-allowances/src/updaters/BalancesWatcherUpdater.tsx
  • libs/balances-and-allowances/src/updaters/NativeTokenBalanceUpdater.test.tsx
  • libs/balances-and-allowances/src/updaters/NativeTokenBalanceUpdater.tsx

Comment thread libs/balances-and-allowances/src/hooks/useBalancesWatcherSession.test.tsx Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant